package org.hawk.gwt.ppc.reflect.lang;

import java.util.HashMap;
import java.util.Map;

/**
 * Replacement for the class loaders of java language. This class main goal is
 * to provide {@link ClassDelegate} a way to load the classes from GWT modules.
 * 
 * @author alex.bereznevatiy@gmail.com
 */
public abstract class ClassLoaderDelegate {
	private Map<String, ClassDelegate<?>> classes;
	private Map<String, Class<?>> notReflectable;

	/**
	 * Method to implement in entry points of each reflected module.
	 * 
	 * @return list of classes to be available for this class loader.
	 */
	protected abstract ClassDelegate<?>[] getClasses();

	/**
	 * This method used to perform higher compile-time
	 * precalculation/optimization. Since usually all classes of specific module
	 * are located either under the same package or have common parent package
	 * its easy to determine wherever this class loader can load the class with
	 * specific name by checking does the package of the class is a subpackage
	 * of this one.
	 * 
	 * @return parent package of all classes available for this class loader.
	 *         General contract is not to return <code>null</code> here. Use
	 *         empty string if you don't what to use this kind of optimization.
	 */
	protected abstract String getPackage();

	/**
	 * This method used to create and load the classes that are not intended to
	 * be reflected but should serve as runtime types for e.g. to check
	 * parameters or return type.
	 * 
	 * @return list of classes available for this class loader but not able to
	 *         be reflected with {@link ClassDelegate}
	 */
	protected abstract Class<?>[] getNotReflectableClasses();

	/**
	 * Find the class by the name passed. If class cannot be found -
	 * ClassNotFoundException will be raised. This implementation differs from
	 * default java implementation since it searches the classes only within the
	 * current class loader.
	 * 
	 * @param name
	 * @return class with name specified (never <code>null</code>)
	 * @throws ClassNotFoundException
	 */
	public ClassDelegate<?> loadClass(String name)
			throws ClassNotFoundException {
		if (name == null || name.length() == 0) {
			throw new ClassNotFoundException(name);
		}
		if (name.indexOf('[') >= 0) {
			return loadArray(name);
		}
		if (!name.startsWith(getPackage())) {
			throw new ClassNotFoundException(name);
		}
		if (classes == null) {
			preloadClasses();
		}
		if (classes.get(name) == null) {
			throw new ClassNotFoundException(name);
		}
		ClassDelegate<?> cls = classes.get(name);
		if (!cls.isLoaded()) {
			cls.load(this);
		}
		return cls;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private ClassDelegate<?> loadArray(final String name)
			throws ClassNotFoundException {
		ClassDelegate<?> arrayType = ClassDelegate.forName(SystemClassLoader
				.getArrayComponentName(name));
		if (arrayType == null) {
			arrayType = loadClass(getComponentName(name));
		}

		return new ArrayClassDelegate(arrayType);
	}

	private String getComponentName(final String name) {
		if (name.indexOf('[', 1) < 0) {
			return name.substring(2, name.length() - 1);
		}
		return name.substring(1);
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected ClassDelegate<?> loadNotReflectableClass(String name) {
		if (notReflectable == null) {
			preloadNotReflectable();
		}
		Class<?> cls = notReflectable.get(name);
		if (cls == null) {
			return null;
		}
		ClassDelegate<?> rez = new ClassDelegate(cls);
		rez.load(this);
		return rez;
	}

	private void preloadNotReflectable() {
		notReflectable = new HashMap<String, Class<?>>();
		for (Class<?> cls : getNotReflectableClasses()) {
			notReflectable.put(cls.getName(), cls);
		}
	}

	private void preloadClasses() {
		classes = new HashMap<String, ClassDelegate<?>>();
		for (ClassDelegate<?> cls : getClasses()) {
			classes.put(cls.getName(), cls);
		}
	}

	/**
	 * @return parent class loader. For this implementation it is always system
	 *         loader (or null if it is already system one).
	 */
	public final ClassLoaderDelegate getParent() {
		if (this == SystemClassLoader.getInstance()) {
			return null;
		}
		return SystemClassLoader.getInstance();
	}
}
